<?php

namespace maotool\util;


use maotool\object\ObjUtil;
use maotool\object\StrUtil;

/**
 * 树结构工具
 */
class TreeUtil
{

    /**
     * 数组转树结构
     * @param $list
     * @param $config
     * @return array
     */
    public static function listToTree($list, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $treeHandle = function ($parent, $list, $treeHandle) use ($config) {
            $topTree = [];
            $childrenList = [];
            foreach ($list as $childrenItem) {
                $parentCount = 0;
                foreach ($list as $parentItem) {
                    if ($parentItem[$config["id"]] != $childrenItem[$config["id"]]) {
                        if ($parentItem[$config["id"]] == $childrenItem[$config["pid"]]) {
                            ++$parentCount;
                            $childrenList[] = $childrenItem;
                        } else if (ObjUtil::isNotNull($config["pid_code"])
                            && in_array(strval($parentItem[$config["id"]]), StrUtil::split($childrenItem[$config["pid_code"]], ","))) {
                            ++$parentCount;
                            $childrenList[] = $childrenItem;
                        }
                    }
                }
                if ($parentCount == 0) {
                    if (ObjUtil::isNull($parent)) {
                        $topTree[] = $childrenItem;
                    } else if ($parent[$config["id"]] == $childrenItem[$config["pid"]]) {
                        $topTree[] = $childrenItem;
                    } else if (ObjUtil::isNotNull($config["pid_code"])
                        && in_array(strval($parent[$config["id"]]), StrUtil::split($childrenItem[$config["pid_code"]], ","))) {
                        $topTree[] = $childrenItem;
                    }
                }
            }
            foreach ($topTree as &$topTreeItem) {
                $childrenList = ArrayUtil::distinct($childrenList, $config["id"]);
                $topTreeItem['children'] = $treeHandle($topTreeItem, $childrenList, $treeHandle);
                $tempChildren = ArrayUtil::diff($childrenList, self::treeToList($topTreeItem['children']), $config["id"]);
                foreach ($tempChildren as $tempChildrenItem) {
                    if ($topTreeItem[$config["id"]] == $tempChildrenItem[$config["pid"]]) {
                        $topTreeItem['children'][] = $tempChildrenItem;
                    } else if (ObjUtil::isNotNull($config["pid_code"])
                        && in_array(strval($topTreeItem[$config["id"]]), StrUtil::split($tempChildrenItem[$config["pid_code"]], ","))) {
                        $topTreeItem['children'][] = $tempChildrenItem;
                    }
                }
            }
            return $topTree;
        };
        return $treeHandle(null, $list, $treeHandle);
    }

    /**
     * 树结构转数组
     * @param $tree
     * @param $config
     * @return array
     */
    public static function treeToList($tree, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "unset_children" => true,
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $listHandle = function ($tree, $config, $listHandle) {
            $list = [];
            foreach ($tree as $item) {
                if (isset($item[$config["children"]])) {
                    $children = $item[$config["children"]];
                    if (isset($config["unset_children"]) && $config["unset_children"]) {
                        unset($item[$config["children"]]);
                    }
                } else {
                    $children = null;
                }
                $list[] = $item;
                if (is_array($children)) {
                    $list = array_merge($list, $listHandle($children, $config, $listHandle));
                }
            }
            return $list;
        };
        return $listHandle($tree, $config, $listHandle);
    }

    /**
     * 查找树结构中指定节点
     * @param $tree
     * @param $self
     * @param $config
     * @return mixed
     */
    public static function findTreeBySelf($tree, $self, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $listHandle = function ($tree, $self, $config, $listHandle) {
            foreach ($tree as $item) {
                if ($item[$config["id"]] == $self[$config["id"]]) {
                    return $item;
                }
                if (is_array($item[$config["children"]])) {
                    $children = $listHandle($item[$config["children"]], $self, $config, $listHandle);
                    if (!is_null($children)) {
                        return $children;
                    }
                }
            }
            return null;
        };
        return $listHandle($tree, $self, $config, $listHandle);
    }

    /**
     * 查找列表结构的指定节点
     * @param $list
     * @param $self
     * @param $config
     * @return array
     */
    public static function findListBySelf($list, $self, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $tree = TreeUtil::listToTree($list, $config);
        return TreeUtil::treeToList(self::findTreeBySelf($tree, $self, $config));
    }

    /**
     * 查找树结构中指定节点
     * @param $tree
     * @param $self
     * @param $config
     * @return mixed
     */
    public static function findTreeByChildren($tree, $self, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $listHandle = function ($tree, $self, $config, $listHandle) {
            foreach ($tree as $item) {
                if ($item[$config["id"]] == $self[$config["id"]]) {
                    if (is_array($item[$config["children"]])) {
                        return $item[$config["children"]];
                    } else {
                        return [];
                    }
                }
                if (is_array($item[$config["children"]])) {
                    $children = $listHandle($item[$config["children"]], $self, $config, $listHandle);
                    if (!is_null($children)) {
                        return $children;
                    }
                }
            }
            return null;
        };
        return $listHandle($tree, $self, $config, $listHandle)?:[];
    }

    /**
     * 查找列表结构的指定节点的下级节点
     * @param $list
     * @param $self
     * @param $config
     * @return array
     */
    public static function findListByChildren($list, $self, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $tree = TreeUtil::listToTree($list, $config);
        return TreeUtil::treeToList(self::findTreeByChildren($tree, $self, $config));
    }

    /**
     * 查找树结构中指定节点的父节点
     * @param $tree
     * @param $self
     * @param $config
     * @return mixed
     */
    public static function findTreeByParent($tree, $self, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $list = TreeUtil::treeToList($tree, $config);
        if (isset($config['pid_code']) && isset($self[$config['pid_code']])) {
            foreach (StrUtil::split($self[$config['pid_code']], ",") as $id) {
                if ($self[$config["id"]] != $id) {
                    foreach ($list as $item) {
                        if ($item[$config["id"]] == $id) {
                            return $item;
                        }
                    }
                }
            }
        } else {
            foreach ($list as $item) {
                if (isset($item[$config["id"]])) {
                    if ($item[$config["id"]] == $self[$config["pid"]]) {
                        return $item;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 查找树结构中指定节点的所有父节点
     * @param $tree
     * @param $self
     * @param $config
     * @return array
     */
    public static function findTreeByParents($tree, $self, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $parents = [];
        $list = TreeUtil::treeToList($tree, $config);
        if (isset($config['pid_code']) && isset($self[$config['pid_code']])) {
            foreach (ArrayUtil::flip(StrUtil::split($self[$config['pid_code']], ",")) as $id) {
                if ($self[$config["id"]] != $id) {
                    foreach ($list as $item) {
                        if ($item[$config["id"]] == $id) {
                            $parents[] = $item;
                        }
                    }
                }
            }
            return $parents;
        } else {
            $index = $self;
            while (true) {
                $isTop = true;
                foreach ($list as $item) {
                    if (isset($item[$config["id"]])) {
                        if ($item[$config["id"]] === $index[$config["pid"]]) {
                            $index = $item;
                            $parents[] = $item;
                            $isTop = false;
                        }
                    }
                }
                if ($isTop) {
                    return $parents;
                }
            }
        }
    }

    /**
     * 获取树结构单个列数据
     * @param $tree
     * @param $column
     * @param $config
     * @return array
     */
    public static function treeToColumn($tree, $column, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $list = self::treeToList($tree, $config);
        $columns = [];
        foreach ($list as $item) {
            $columns[] = $item[$column];
        }
        return $columns;
    }

    /**
     * 查找包含字段值的数据
     * @param $tree
     * @param $key
     * @param $value
     * @param $config
     * @return mixed|null
     */
    public static function findOne($tree, $key, $value, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $listHandle = function ($tree, $key, $value, $config, $listHandle) {
            foreach ($tree as $item) {
                if ($item[$key] == $value) {
                    return $item;
                }
                if (is_array($item[$config["children"]])) {
                    $children = $listHandle($item[$config["children"]], $key, $value, $config, $listHandle);
                    if (!is_null($children)) {
                        return $children;
                    }
                }
            }
            return null;
        };
        return $listHandle($tree, $key, $value, $config, $listHandle);
    }

    /**
     * 查找包含字段值的数据
     * @param $tree
     * @param $key
     * @param $value
     * @param $config
     * @return array
     */
    public static function findAll($tree, $key, $value, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $list = [];
        $listHandle = function ($tree, $key, $value, $config, $listHandle) use (&$list) {
            foreach ($tree as $item) {
                if ($item[$key] == $value) {
                    $list[] = $item;
                }
                if (is_array($item[$config["children"]])) {
                    $listHandle($item[$config["children"]], $key, $value, $config, $listHandle);
                }
            }
        };
        $listHandle($tree, $key, $value, $config, $listHandle);
        return $list;
    }

    /**
     * 下级全部节点
     * @return array
     */
    public static function nextChildrenAll($tree, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $nextTree = [];
        foreach ($tree as $treeItem) {
            if (isset($treeItem['children']) && is_array($treeItem['children'])) {
                foreach ($treeItem['children'] as $item) {
                    $nextTree[] = $item;
                }
            }
        }
        return $nextTree;
    }

    /**
     * 递归遍历数据
     * @param $paramOrTree
     * @param $mainNext
     * @param $node
     * @return mixed
     */
    public static function recursion($paramOrTree, $mainNext, $node = ["level" => 0, "top" => true, "parent" => null, "parents" => []])
    {
        return $mainNext($paramOrTree, function ($paramOrTree, $parent) use ($mainNext, $node) {
            $node["level"] = $node["level"] + 1;
            $node["top"] = false;
            $node["parent"] = $parent;
            $node["parents"] = ArrayUtil::merge($node["parents"], [$parent]);
            return self::recursion($paramOrTree, $mainNext, $node);
        }, $node);
    }

    /**
     * 递归遍历数据
     * @param $tree
     * @param $func
     * @param array $config
     */
    public static function recursionEach($tree, $func, $config = [
        "id" => "id",
        "name" => null,
        "pid" => "pid",
        "children" => "children",
        "pid_name" => null,
        "pid_code" => null,
        "pid_level" => null,
    ])
    {
        $treeHandle = function ($tree, $treeHandle, $node = ["level" => 0, "top" => true, "parent" => null, "parents" => []]) use ($func, $config) {
            foreach ($tree as &$item) {
                $func($item, $node);
                if (array_key_exists($config["children"], $item) && is_array($item[$config["children"]])) {
                    $treeHandle($item[$config["children"]], $treeHandle, [
                        "parent" => $item,
                        "parents" => ArrayUtil::merge($node["parents"], [$item]),
                        "level" => $node["level"] + 1,
                        "top" => false,
                    ]);
                }
            }
        };
        $treeHandle($tree, $treeHandle);
    }

}